﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace DigitalImage.softLib.util {
    public class CBackPropagation {
        public int MaxVelicinaSloja {      //Maximalan broj neurona po slojevima
            get;
            set;
        }
        public int BrojSlojeva {   //Ukupan broj slojeva
            get;
            set;
        }
        public int BrojUzoraka {   //Ukupan broj uzoraka
            get;
            set;
        }
        public int[] VelicinaSlojeva {
            get;
            set;
        }

        public static int MAXITERACIJA=100000;  //Maximalan broj iteracija za obucavanje
        public static double MAXGRESKA=0.05; //Zadata vrednost maximalne greske
        public static int BROJPOKUSAJA=100;

        public CTSloj[] slojevi; //Svi slojevi u mrezi
        public double[, ,] tezine;  //Sve matrice tezina
        public double[, ,] tezineP; //Matrice promena odgovarajucih tezina
        public double[, ,] obucavajuciSkup;// = new double[uzorciN, 2, MAXN];//
        public double ni;  //brzina obucavanja
        public double beta;//uticaj prethodnih promena

        public CBackPropagation(double[, ,] obucavajuciSkup,int[] velicinaSlojeva) {
            MaxVelicinaSloja=64;
            BrojSlojeva=3;
            BrojUzoraka=4;

            VelicinaSlojeva=velicinaSlojeva;
            slojevi=new CTSloj[BrojSlojeva];
            tezine=new double[BrojSlojeva-1, MaxVelicinaSloja, MaxVelicinaSloja];
            tezineP=new double[BrojSlojeva-1, MaxVelicinaSloja, MaxVelicinaSloja]; 

            BrojUzoraka=obucavajuciSkup.GetLength(0);
            this.obucavajuciSkup=obucavajuciSkup;
            inicijalizacija();
        }

        protected void inicijalizacija() {
            for(int i=0;i<BrojSlojeva;i++)
                slojevi[i]=new CTSloj(VelicinaSlojeva[i], MaxVelicinaSloja);
            ni=0.05;
            beta=0.5;
        }

        protected void inicijalizacijaTezina() {
            Random rnd=new Random();
            for(int s=0;s<BrojSlojeva-1;s++)
                for(int u=0;u<slojevi[s].n;u++)
                    for(int v=0;v<slojevi[s+1].n;v++) {//Inicijalizacija matrice tezina po preporuci W je element [-0.1, 0.1]
                        tezine[s, u, v]=(float)(rnd.NextDouble()-0.5)/5;
                        tezineP[s, u, v]=0;
                    }//NextDouble()  - returns a random number between 0.0 and 1.0

            for(int s=0;s<BrojSlojeva;s++)
                for(int u=0;u<MaxVelicinaSloja;u++) {
                    slojevi[s].bias[u]=0;
                    slojevi[s].biasp[u]=0;
                }

            for(int s=1;s<BrojSlojeva-1;s++)
                for(int u=0;u<slojevi[s].n;u++)
                    slojevi[s].bias[u]=(float)rnd.NextDouble();
            //Inicijalizacija pragova aktivacije. [0,1]
        }

        double sigmoid(double net) {
            return 1/(1+Math.Exp(-net));
        }
        
        double[] izracunajIzlaz() {
            double net=0.0;
            for(int s=1;s<BrojSlojeva;s++)
                for(int v=0;v<slojevi[s].n;v++) {
                    net=slojevi[s].bias[v];
                    for(int u=0;u<slojevi[s-1].n;u++) {
                        net+=slojevi[s-1].izlaz[u]*tezine[s-1, u, v];
                    }
                    slojevi[s].izlaz[v]=sigmoid(net);
                }

            return slojevi[BrojSlojeva-1].izlaz;
        }

        void postaviUlaz(int uzorak) {
            for(int u=0;u<slojevi[0].n;u++)
                slojevi[0].izlaz[u]=obucavajuciSkup[uzorak, 0, u];
        }

        double izracunajGreske(int uzorak) {
            double greska=0.0;
            // greska u izlaznom sloju
            for(int v=0;v<slojevi[BrojSlojeva-1].n;v++) {
                slojevi[BrojSlojeva-1].delta[v]=obucavajuciSkup[uzorak, 1, v]-slojevi[BrojSlojeva-1].izlaz[v];
                greska+=(slojevi[BrojSlojeva-1].delta[v])*(slojevi[BrojSlojeva-1].delta[v]);
            }
            // greska u ostalim slojevima
            for(int s=BrojSlojeva-2;s>=0;s--)
                for(int u=0;u<slojevi[s].n;u++) {
                    double sigmaa=0.0;
                    for(int v=0;v<slojevi[s+1].n;v++)
                        sigmaa+=slojevi[s+1].delta[v]*tezine[s, u, v];
                    double f=slojevi[s].izlaz[u];
                    slojevi[s].delta[u]=f*(1-f)*sigmaa;
                }
            return greska;
        }

        void korigujTezine() {
            for(int s=0;s<BrojSlojeva-1;s++)
                for(int v=0;v<slojevi[s+1].n;v++) {
                    for(int u=0;u<slojevi[s].n;u++) {
                        tezineP[s, u, v]=ni*slojevi[s+1].delta[v]*slojevi[s].izlaz[u]+beta*tezineP[s, u, v];
                        tezine[s, u, v]+=tezineP[s, u, v];
                    }
                    slojevi[s+1].biasp[v]=ni*slojevi[s+1].delta[v]+
                                            beta*slojevi[s+1].biasp[v];
                    slojevi[s+1].bias[v]+=slojevi[s+1].biasp[v];
                    // izracunavanje korigovane vrednosti izlaza u sloju
                    double net=slojevi[s+1].bias[v];
                    for(int u=0;u<slojevi[s].n;u++)
                        net+=slojevi[s].izlaz[u]*tezine[s, u, v];
                    slojevi[s+1].izlaz[v]=sigmoid(net);
                }
        }

        double obucavanje() {
            greske=new List<PointF>();
            double greska=0;
            for(int iteracija=0;iteracija<MAXITERACIJA;iteracija++) {
                greska=0;
                for(int uzorak=0;uzorak<BrojUzoraka;uzorak++) {
                    postaviUlaz(uzorak);
                    izracunajIzlaz();
                    greska+=0.5*izracunajGreske(uzorak);
                    korigujTezine();
                }
                //Console.WriteLine("" + iteracija + " " + greska);
                greske.Add(new PointF(iteracija, (float)greska));
                if(greska<MAXGRESKA)
                    break;
            }
            return greska;
        }

        public List<PointF> greske=null;

        public void obuci() {
            for(int pokusaj=0;pokusaj<BROJPOKUSAJA;pokusaj++) {
                inicijalizacijaTezina();
                double greska=obucavanje();
                if(greska<MAXGRESKA)
                    break;
            }
        }

        public double[] izracunaj(double[] data) {
            for(int i=0;i<data.Length;i++) {
                slojevi[0].izlaz[i]=data[i];
            }

            return izracunajIzlaz();
        }

        public int izracunajIndeks(double[] data) {
            double[] d=izracunaj(data);
            double max=0;
            int index=0;
            for(int i=0;i<13;i++) {
                if(d[i]>max) {
                    max=d[i];
                    index=i;
                }
            }

            return index;
        }
    }

    public class CTSloj  //Tip u kojem se opisuje jedan sloj
    {
        public CTSloj(int stvarniBrojSlojeva,int maksimalanBrojSlojeva) {
            izlaz=new double[maksimalanBrojSlojeva];
            delta=new double[maksimalanBrojSlojeva];
            bias=new double[maksimalanBrojSlojeva];
            biasp=new double[maksimalanBrojSlojeva];
            n=stvarniBrojSlojeva;
        }
        public int n;  //Stvaran broj neurona u sloju
        public double[] izlaz;
        public double[] delta; //Greska u posmatranom cvoru
        public double[] bias;  //Prag aktivacije
        public double[] biasp; //Promena praga aktivacije
    }
}
